package com.sld.integrate.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.iot.model.v20180120.SetDevicePropertyRequest;
import com.aliyuncs.iot.model.v20180120.SetDevicePropertyResponse;
import com.sld.integrate.domain.RecDevMessage;
import com.sld.integrate.domain.RecDevWorkSequence;
import com.sld.integrate.mapper.RecDevMessageMapper;
import com.sld.integrate.service.IDevWorkSequenceService;
import com.sld.integrate.service.IRecDevCalculateService;
import com.sld.integrate.service.MessageConsumerService;
import com.sld.integrate.vo.CalculateTuple;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 描述:
 *
 * @Author tanli
 * @create 2022-02-24 12:36
 */
@Service
@Slf4j
public class MessageConsumerServiceImpl implements MessageConsumerService {


    public static final String CALIBRATION_KEY = "list:calibration:";
    public static final String TRIGGER_CALIBRATION_EVENT = "string:trigger_calibration_event:";

    public static final Integer WINDOW_SIZE = 7;

    @Resource
    RecDevMessageMapper detectMapper;

    @Resource
    IRecDevCalculateService calculateService;

    @Resource
    IDevWorkSequenceService workSequenceService;

    @Resource
    DefaultAcsClient client;

    @Resource
    RedisTemplate redisTemplate;

    @Value("${alibaba.iot.iotInstanceId}")
    String iotInstanceId;

    @Override
    @Transactional
    public void saveMessage(String topic, String messageId, String body) throws Exception {
        log.info("process message: topic={},messageId={},\n content={}", topic, messageId, body);
        //1. 入库存储基本的信息 todo 根据新协议重新修改存储表格和协议转换。
        JSONObject json = JSONObject.parseObject(body);
        RecDevMessage recDevMessage = devMessageConvert(messageId, json);
        detectMapper.insert(recDevMessage);

        String devId = recDevMessage.getDevId();
        int state = recDevMessage.getState();


        RecDevWorkSequence sequence = workSequenceService.queryDevRecentlyWorkInfo(devId);

        //1. 反转信号，直接关闭任务，回溯工地测量值
        if (state == 2) {
            if (sequence != null) {
                sequence.setEndTime(new Date()); //无论如何需要结束掉上一个工作序号
                workSequenceService.update(sequence);
                calculateService.updateDestinationDetectValue(sequence.getId(), 120);//更新本次运输任务的工地测量值，从后面向前找
                log.info("【结束运输序列】state=2, devId={}, seq={}，(可能反复标志2，反复更新结束时间)", devId, sequence.getWorkSequence());
            } else {
                log.error("【结束运输序列】该设备没有对应的工作序列记录，一条都没有！！！");
            }
            return;
        }

        //2. 如果是定时数据，单独计算处理，无需下发坍损值。
        if (state == 3) {
            calculateService.executeTimerCalculate(sequence,recDevMessage);
            return;
        }

        //3. 标定测量值。state=1 是一次运输的开始。新的运输任务开始，并且启动标定值测量
        if (state == 1) {
            // 如果存在未结束的任务且开始时间已经超过2分钟了，那么结束上一个任务，重新生成一个新的工作序列。（这里可能会有多个1连续出现的保护）
            sequence = workSequenceService.createOrGetRecentlyWorkSequence(devId);
            //尝试关联混凝土注册信息
            int regId = workSequenceService.createWorkSequenceConcreteBinding(sequence);
            log.info("混凝土注册信息关联regId={}(-1表示未关联上)，seqId={}", regId, sequence.getId());
        }

        //todo 这个逻辑有问题？？？？？？？？？？？
        //3. 尝试计算标定值，可能累积够了CALIBRATION，注意，计算之后，需要删除这个key。
        CalculateTuple tuple = calibrationCalculate(recDevMessage);
        if (tuple != null) {
            log.info("devId={}计算标定值结果为p={},s={}", devId, tuple.getPressure(), tuple.getSpeed());
            //记录标定值，并与工作序列关联。
            if (sequence != null) {
                workSequenceService.saveCalibrationValue(sequence.getId(), tuple);
            }
        }

        if (state == 0 && sequence == null) {
            // 无工作序列创建的过程信号，说明需要主动触发反光板信号。监听变化值。
            log.info("devId={}无任务创建，但state=0，说明在商混站没有反光板触发。我们这里补偿。根据压力变化量下发通知",devId);
            setCalibrationEvent(recDevMessage);
            return;
        }


        //下面就是正常情况，累积计算数据，达到窗口和数据量就计算
        int value = calculateService.executeStaticCalculate(sequence, recDevMessage);
        if (value < 0) {
            return;
        }
        String productKey = json.getString("productKey"); // todo：通知坍损值
//        if (productKey != null) {
//            JSONObject jsonItems = new JSONObject();
//            jsonItems.put("TanLuo", value);
//            //通知到前端车辆终端
//            noticeDevice(productKey, devId, jsonItems.toJSONString());
//        }
    }

    private CalculateTuple calibrationCalculate(RecDevMessage recDevMessage) {
        String devId = recDevMessage.getDevId();
        // 启动本次运输序列的标定值测量窗口计算, 要考虑反复=1的情况。1.5分钟标定值计算窗口期
        String key = CALIBRATION_KEY + devId;
        int state = recDevMessage.getState();
        if (state == 2) {
            return null;
        }

        if (state == 1) {
            redisTemplate.opsForList().rightPush(key, recDevMessage);
            log.info("存储一个state=1的标定值，msg={}", JSONObject.toJSONString(recDevMessage));

            Long ttl = redisTemplate.getExpire(key);
            if (ttl == -1) { // 未设置过期时间，必须设置
                redisTemplate.expire(key, 90, TimeUnit.SECONDS);
                log.info("该标定值是全新的开始，是第一个。设置窗口期90秒，msgID={}", recDevMessage.getMsgId());
            }
        }
        if (state == 0 && redisTemplate.hasKey(key)) {
            redisTemplate.opsForList().rightPush(key, recDevMessage);
            log.info("存储一个state=0的标定值，msg={}", JSONObject.toJSONString(recDevMessage));
        }

        List list = redisTemplate.opsForList().range(key, 0, WINDOW_SIZE - 1);

        if (list.size() < WINDOW_SIZE) {
            log.info("devId={}, 标定值累积{}, 不够7个，不计算标定值。", devId, list.size());
            return null;
        }
        // 四元组均值计算
        CalculateTuple tuple = calculateService.tupleAvgCalculate(list, true);
        Boolean res = redisTemplate.delete(key);
        log.info("debug：计算标定值，删除key={},删除结果={}", key, res);
        return tuple;
    }




    private RecDevMessage devMessageConvert(String messageId, JSONObject json) {
        String devId = json.getString("deviceName");
        Date msgDate = new Date(json.getLong("gmtCreate"));
        JSONObject items = json.getJSONObject("items");
        float rotateSpeed = items.getJSONObject("ZhuanSu").getFloatValue("value");
        float humidity = items.getJSONObject("ShiDu").getFloatValue("value");
        float temperature = items.getJSONObject("WenDu").getFloatValue("value");
        float pressure = items.getJSONObject("YaLi").getFloatValue("value");
        JSONObject stateJson = items.getJSONObject("QiTa");
        int state = stateJson == null ? 0 : stateJson.getIntValue("value"); // 1 开始  0中间态 2结束

        RecDevMessage recDevMessage = new RecDevMessage(Long.parseLong(messageId), devId, pressure, rotateSpeed, humidity, temperature, state, msgDate, new Date());
        return recDevMessage;
    }



    @Async
    @Override
    public void noticeDevice(String productKey, String deviceName, String items) throws Exception{
        log.info(">> notify Ali-Iot setDeviceProperty. deviceName:{},items={}", deviceName, items);
        SetDevicePropertyRequest propertyRequest = new SetDevicePropertyRequest();
        propertyRequest.setIotInstanceId(iotInstanceId);
        propertyRequest.setProductKey(productKey);
        propertyRequest.setDeviceName(deviceName);
        propertyRequest.setActionName("SetDeviceProperty");
        propertyRequest.setItems(items);

        SetDevicePropertyResponse response = client.getAcsResponse(propertyRequest);
        log.info(">> notify ali iot setDeviceProperty. respon={}", response.getSuccess());

//        InvokeThingServiceRequest request;
        //https://help.aliyun.com/document_detail/131911.html
    }



    @Override
    public void setCalibrationEvent(RecDevMessage recDevMessage) {
        String title = "【站内监听压力变化量】";
        String devId = recDevMessage.getDevId();
        String key = TRIGGER_CALIBRATION_EVENT + devId;

        String value = (String) redisTemplate.opsForValue().get(key);
        RecDevMessage previousMessage = null;

        if (StringUtils.isEmpty(value)) {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(recDevMessage), 3, TimeUnit.MINUTES);
            log.info("{}devId={}.压力={}该值作为第一个数值.",title, devId, recDevMessage.getPressure());
            return;
        }
        previousMessage = JSONObject.parseObject(value, RecDevMessage.class);

        double delta = recDevMessage.getPressure() - previousMessage.getPressure();
        log.info("{}devId={}, 上次压力={},本次压力={}，delta={}", title, devId, previousMessage.getPressure(), recDevMessage.getPressure(), delta);
        if (delta > 2) {
            //noticeDevice(); // todo 下发信号量
            log.info("{}变化量达标，需要通知给设备",title);
            return;
        }
        //替换成最新的
        redisTemplate.opsForValue().set(key, JSON.toJSONString(recDevMessage));
        log.info("{}变化量不够，替换原值，等待下一次判断", title);
    }
}
